﻿using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.Animations;
using UnityEditor.U2D;
using UnityEditor.U2D.Sprites;
using UnityEngine;
using UnityEngine.U2D;
using UnityEngine.U2D.Animation;

namespace Game.Editor
{
    public class PetSpritePostprocessor : AssetPostprocessor
    {
        private static readonly string _PathPattern = @"(Assets/Game/Assets/Pets/\d+/)(\d+).png";

        private static readonly string _ControllerAssetPath =
            "Assets/Game/Assets/Pets/Template/Animations/PetTemplateAnimator.controller";
        
        private static List<string> _toProcessPathList = new List<string>();

        public static void PostprocessBoneSprite(string assetPath, string pathPattern, string controllerAssetPath, AssetImporter assetImporter, Sprite sprite)
        {
            SpriteDataProviderFactories dataProviderFactories = new SpriteDataProviderFactories();
            dataProviderFactories.Init();
            ISpriteEditorDataProvider ai = dataProviderFactories.GetSpriteEditorDataProviderFromObject(assetImporter);
            if (ai != null)
            {
                ai.InitSpriteEditorDataProvider();
                PostProcessBoneData(ai, sprite);
                PostProcessSpriteMeshData(ai, sprite);
                ai.Apply();
                EditorApplication.delayCall += () =>
                {
                    CreatePrefab(assetPath, pathPattern, controllerAssetPath);
                };
            }
        }
        
        private void OnPostprocessTexture(Texture2D texture)
        {
            MatchCollection matchCollection = Regex.Matches(assetPath, _PathPattern);
            if (matchCollection.Count > 0)
            {
                TextureImporter textureImporter = (TextureImporter) assetImporter;
                if (textureImporter.textureType == TextureImporterType.Sprite)
                {
                    return;
                }
                textureImporter.textureType = TextureImporterType.Sprite;
                _toProcessPathList.Add(assetPath);
                assetImporter.SaveAndReimport();
            }
        }

        private void OnPostprocessSprites(Texture2D texture, Sprite[] sprites)
        {
            if (!_toProcessPathList.Contains(assetPath))
            {
                return;
            }
            _toProcessPathList.Remove(assetPath);
            PostprocessBoneSprite(assetPath, _PathPattern, _ControllerAssetPath, assetImporter, sprites[0]);
        }

        private static void CreatePrefab(string assetPath, string pathPattern, string controllerAssetPath)
        {
            MatchCollection matchCollection = Regex.Matches(assetPath, pathPattern);
            string directory = matchCollection[0].Groups[1].Value;
            string name = matchCollection[0].Groups[2].Value;
            GameObject gameObject = new GameObject(name);
            GameObject spriteGO = new GameObject("Sprite");
            spriteGO.transform.SetParent(gameObject.transform, false);
            SpriteRenderer spriteRenderer = spriteGO.AddComponent<SpriteRenderer>();
            spriteRenderer.sprite = AssetDatabase.LoadAllAssetsAtPath(assetPath)[1] as Sprite;
            SpriteSkin spriteSkin = spriteGO.AddComponent<SpriteSkin>();
            GameObject bone_1 = new GameObject("bone_1");
            bone_1.transform.SetParent(spriteGO.transform);
            GameObject bone_2 = new GameObject("bone_2");
            bone_2.transform.SetParent(bone_1.transform);
            GameObject bone_3 = new GameObject("bone_3");
            bone_3.transform.SetParent(bone_1.transform);
            Rebind(spriteSkin, bone_1.transform, spriteRenderer.sprite);
            ResetBindPose(spriteSkin, spriteRenderer.sprite);
            spriteGO.transform.localEulerAngles = new Vector3(0f, 0f, 90f);
            Animator animator = gameObject.AddComponent<Animator>();
            animator.runtimeAnimatorController = AssetDatabase.LoadAssetAtPath<AnimatorController>(controllerAssetPath);
            BoxCollider2D boxCollider2D = gameObject.AddComponent<BoxCollider2D>();
            boxCollider2D.size = spriteRenderer.sprite.bounds.size;
            boxCollider2D.offset = new Vector2(0f, boxCollider2D.size.y * 0.5f);
            string prefabAssetPath = directory + name + ".prefab";
            PrefabUtility.SaveAsPrefabAsset(gameObject, prefabAssetPath);
            Object.DestroyImmediate(gameObject);
        }

        static void PostProcessBoneData(ISpriteEditorDataProvider spriteDataProvider, Sprite sprite)
        {
            var boneDataProvider = spriteDataProvider.GetDataProvider<ISpriteBoneDataProvider>();
            var textureDataProvider = spriteDataProvider.GetDataProvider<ITextureDataProvider>();

            if (sprite == null || boneDataProvider == null || textureDataProvider == null)
                return;

            var guid = sprite.GetSpriteID();
            var spriteBone = new List<SpriteBone>();
            Bounds bounds = sprite.bounds;

            SpriteBone parentBone = new SpriteBone();
            parentBone.parentId = -1;
            parentBone.position = new Vector3(bounds.extents.x * 100f, 0f, 0f);
            parentBone.rotation = new Quaternion(0f, 0f, Mathf.Cos(Mathf.Deg2Rad * 45f), Mathf.Sin(Mathf.Deg2Rad * 45f));
            parentBone.length = 30f;
            parentBone.name = "bone_1";
            spriteBone.Add(parentBone);

            SpriteBone leftBone = new SpriteBone();
            leftBone.parentId = 0;
            leftBone.position = new Vector3(0f, bounds.extents.x * 100f, 0f);
            leftBone.rotation = new Quaternion(0f, 0f, Mathf.Cos(Mathf.Deg2Rad * 90f), Mathf.Sin(Mathf.Deg2Rad * 90f));
            leftBone.length = bounds.size.y * 100f;
            leftBone.name = "bone_2";
            spriteBone.Add(leftBone);
            
            SpriteBone rightBone = new SpriteBone();
            rightBone.parentId = 0;
            rightBone.position = new Vector3(0f, -bounds.extents.x * 100f, 0f);
            rightBone.rotation = new Quaternion(0f, 0f, Mathf.Cos(Mathf.Deg2Rad * 90f), Mathf.Sin(Mathf.Deg2Rad * 90f));
            rightBone.length = bounds.size.y * 100f;
            rightBone.name = "bone_3";
            spriteBone.Add(rightBone);
            
            boneDataProvider.SetBones(guid, spriteBone);
        }
        
        static void PostProcessSpriteMeshData(ISpriteEditorDataProvider spriteDataProvider, Sprite sprite)
        {
            var spriteMeshDataProvider = spriteDataProvider.GetDataProvider<ISpriteMeshDataProvider>();
            var boneDataProvider = spriteDataProvider.GetDataProvider<ISpriteBoneDataProvider>();
            var textureDataProvider = spriteDataProvider.GetDataProvider<ITextureDataProvider>();
            if (sprite == null || spriteMeshDataProvider == null || textureDataProvider == null)
                return;

            Bounds bounds = sprite.bounds;
            var guid = sprite.GetSpriteID();
            var leftBottomVertex = new Vertex2DMetaData();
            leftBottomVertex.position = new Vector2(0f, 0f);
            var boneWeight = new BoneWeight();
            boneWeight.weight0 = 1;
            boneWeight.weight1 = 0;
            boneWeight.weight2 = 0;
            boneWeight.weight3 = 0;
            boneWeight.boneIndex0 = 1;
            boneWeight.boneIndex1 = 0;
            boneWeight.boneIndex2 = 0;
            boneWeight.boneIndex3 = 0;
            leftBottomVertex.boneWeight = boneWeight;
            
            var rightBottomVertex = new Vertex2DMetaData();
            rightBottomVertex.position = new Vector2(bounds.size.x * 100f, 0f);
            boneWeight = new BoneWeight();
            boneWeight.weight0 = 1;
            boneWeight.weight1 = 0;
            boneWeight.weight2 = 0;
            boneWeight.weight3 = 0;
            boneWeight.boneIndex0 = 2;
            boneWeight.boneIndex1 = 0;
            boneWeight.boneIndex2 = 0;
            boneWeight.boneIndex3 = 0;
            rightBottomVertex.boneWeight = boneWeight;

            var leftTopVertex = new Vertex2DMetaData();
            leftTopVertex.position = new Vector2(0f, bounds.size.y * 100f);
            boneWeight = new BoneWeight();
            boneWeight.weight0 = 1;
            boneWeight.weight1 = 0;
            boneWeight.weight2 = 0;
            boneWeight.weight3 = 0;
            boneWeight.boneIndex0 = 1;
            boneWeight.boneIndex1 = 0;
            boneWeight.boneIndex2 = 0;
            boneWeight.boneIndex3 = 0;
            leftTopVertex.boneWeight = boneWeight;
            
            var rightTopVertex = new Vertex2DMetaData();
            rightTopVertex.position = new Vector2(bounds.size.x * 100f, bounds.size.y * 100f);
            boneWeight = new BoneWeight();
            boneWeight.weight0 = 1;
            boneWeight.weight1 = 0;
            boneWeight.weight2 = 0;
            boneWeight.weight3 = 0;
            boneWeight.boneIndex0 = 2;
            boneWeight.boneIndex1 = 0;
            boneWeight.boneIndex2 = 0;
            boneWeight.boneIndex3 = 0;
            rightTopVertex.boneWeight = boneWeight;

            var vertices = new [] {leftBottomVertex, rightBottomVertex, leftTopVertex, rightTopVertex};
            spriteMeshDataProvider.SetVertices(guid, vertices);
            spriteMeshDataProvider.SetIndices(guid, new []{0, 1, 2, 1, 2, 3});
            spriteMeshDataProvider.SetEdges(guid, new Vector2Int[0]);
        }
        
        static void Rebind(SpriteSkin spriteSkin, Transform rootBone, Sprite sprite)
        {
            var skinSerializedObject = new SerializedObject(spriteSkin);
            var rootBoneProperty = skinSerializedObject.FindProperty("m_RootBone");
            rootBoneProperty.objectReferenceValue = rootBone;

            var spriteBones = sprite.GetBones();
            // var boneTransforms = new List<Transform>();

            var boneTransformsProperty = skinSerializedObject.FindProperty("m_BoneTransforms");
            for (int i = 0; i < spriteBones.Length; ++i)
            {
                var boneTransformPath = CalculateBoneTransformPath(i, spriteBones);
                var boneTransform = rootBone.Find(boneTransformPath);

                boneTransformsProperty.InsertArrayElementAtIndex(i);
                boneTransformsProperty.GetArrayElementAtIndex(i).objectReferenceValue = boneTransform;
            }
            skinSerializedObject.ApplyModifiedProperties();
        }
        
        static void ResetBindPose(SpriteSkin spriteSkin, Sprite sprite)
        {
            var spriteBones = sprite.GetBones();
            var boneTransforms = spriteSkin.boneTransforms;

            for (int i = 0; i < boneTransforms.Length; ++i)
            {
                var boneTransform = boneTransforms[i];
                var spriteBone = spriteBones[i];

                if (spriteBone.parentId != -1)
                {
                    boneTransform.localPosition = spriteBone.position;
                    boneTransform.localRotation = spriteBone.rotation;
                    boneTransform.localScale = Vector3.one;
                }
            }
        }
        
        static string CalculateBoneTransformPath(int index, SpriteBone[] spriteBones)
        {
            var path = "";

            while (index != -1)
            {
                var spriteBone = spriteBones[index];
                var spriteBoneName = spriteBone.name;
                if (spriteBone.parentId != -1)
                {
                    if (string.IsNullOrEmpty(path))
                        path = spriteBoneName;
                    else
                        path = spriteBoneName + "/" + path;
                }
                index = spriteBone.parentId;
            }

            return path;
        }
    }
}